#include "Collection.h"
#include "Tools.h"

#include "Log.h"

#include "Item.h"
#include "Item_Word.h"
#include "Item_Point.h"
#include "Item_Gene.h"

#include "Metadata.h"
#include "Metadata_Point.h"
#include "Metadata_Point2.h"
#include "Metadata_Gene.h"
#include "Metadata_Word.h"

#include <string>
#include <iostream>
#include <fstream>
#include <limits>

#include "Log.h"
#include "PS_Log.h"

Collection::Collection(Container::Type cont_type) {
    container_type = cont_type;
    switch (container_type) {
    case Container::VECTOR:
        v_items = new vector<Item*>();
        break;

    case Container::LIST:
        l_items = new list<Item*>();
        break;

    case Container::SET:
        s_items = new set<Item*>();
        break;

    case Container::HASH:
        h_items = new hash_map<Item*, Item*>();
        break;
    }
}

void Collection::clear() {
    switch (container_type) {
    case Container::VECTOR:
        v_items->clear();
        break;

    case Container::LIST:
        l_items->clear();
        break;

    case Container::SET:
        s_items->clear();
        break;

    case Container::HASH:
        h_items->clear();
        break;
    }
}

void Collection::add(Item *item) {
    switch (container_type) {
    case Container::VECTOR:
        v_items->push_back(item);
        break;

    case Container::LIST:
        l_items->push_back(item);
        break;

    case Container::SET:
        s_items->insert(item);
        break;

    case Container::HASH:
        h_items->[item] = item;
        break;
    }
}

void Collection::add(Collection& other) {
    switch (container_type) {
    case Container::VECTOR:
        v_items->push_back(item);
        break;

    case Container::LIST:
        l_items->push_back(item);
        break;

    case Container::SET:
        s_items->insert(item);
        break;

    case Container::HASH:
        h_items->[item] = item;
        break;
    }
}

Collection::~Collection() {
     clear();
}

 void Collection::read(const std::string& filename) {
     LOG_ENTER(INPUT,"Collection::read");
     LOG(INPUT) << "read(\"" << filename << "\")";

     std::ifstream file(filename.c_str());

     if (!file) {
         std::cerr << "Can't read from " << filename << std::endl;

         LOG_EXIT();
         exit(EXIT_FAILURE);
     }

     LOG(INPUT) << "opened file OK";

     std::string line;

     // read the magic code on the first line of data
     getline(file, line);
     while (line.find_last_of('\r') != -1) {
         line.erase(line.length() - 1);
     }

     LOG(INPUT) << "magic # is " << line;

     // Decode the magic code to get the kind of data stored in the file
     Metric_Object::Type item_type = Metric_Object::UNKNOWN;
    if      (iequals(line, "point"))
        item_type = Metric_Object::POINT;
    else if (iequals(line, "point2"))
        item_type = Metric_Object::POINT2;
    else if (iequals(line, "word"))
        item_type = Metric_Object::WORD;
    else if (iequals(line, "gene"))
        item_type = Metric_Object::WORD;

    LOG(INPUT) << "item type is " << item_type;

    // parse the metadata at the top of the file
    Metadata *metadata;
    switch (item_type) {
    case Metric_Object::POINT:  metadata = new Metadata_Point();  break;
    case Metric_Object::POINT2: metadata = new Metadata_Point2(); break;
    case Metric_Object::WORD:   metadata = new Metadata_Word();   break;
    case Metric_Object::GENE:   metadata = new Metadata_Gene();   break;
    default:
        LOG(ERROR) << "Unknown magic # starting input file " << filename;
        throw std::string("Unknown magic # starting input file ")+filename;
    }

    LOG(INPUT) << "made metadata OK";

    metadata->parse(file);
    // Now read the items (one per line in the file)
    int nItems = 0;
    std::string obj_name;
    clear();

    LOG(INPUT) << "parsed header";

    while (!file.eof()) {
        getline(file, line);
        while (line.find_last_of('\r') != -1) {
            line.erase(line.length() - 1);
        }

        LOG(INPUT) << "got data line \"" << line << "\"";

        if (line == "") break;

        Item *item;
        switch (item_type) {
        case Metric_Object::POINT:
        case Metric_Object::POINT2: item = new Point_Item(); break;
        case Metric_Object::WORD:   item = new Word_Item();  break;
        case Metric_Object::GENE:   item = new Gene_Item();  break;
        }
        item->parse(line, *metadata, obj_name);
        item->init_label(obj_name, nItems);

        LOG(INPUT) << "  created item=" << item;

        add(item);
        nItems++;
    }

    LOG(INPUT) << "got " << nItems << " total";
    LOG(INPUT) << "read all data OK";

    // Go through the list, with two purposes:
    // 1. set item positions, for visualization purposes
    // 2. save iterators for first and last entries
    double dtheta = 2.0 * 3.1416 / nItems;

    double xmin =  std::numeric_limits<double>::max();
    double ymin =  xmin;
    double xmax = -xmin;
    double ymax = -ymin;

    int index = 0;
    Collection_Span span = get_whole_span();
    for (Item_Iterator iter = span.start; index < span.n; iter++, index++) {

        LOG(INPUT) << "  setting coords for index=" << index;

        // if (index == 0) {
        //     first = iter;
        // }
        // else if (index == nItems-1) {
        //     last = iter;
        // }
        Item *item = *iter;

        LOG(INPUT) << "  got item=" << item;

        double x,y;
        if (item_type == Metric_Object::POINT2) { // 2D points know their positions

            LOG(INPUT) << "  it's a POINT2";

            Item_Point& p = *((Item_Point*)(item->object));

            LOG(INPUT) << "  cast succeeded p=" << p;

            x = p[0];
            y = p[1];
        }
        else { // every other type will be on a circle
            double theta = index * dtheta;
            x = 0.5 + 0.5 * cos(theta);
            y = 0.5 + 0.5 * sin(theta);
        }
        item->x = x;
        item->y = y;

        if (x > xmax) xmax = x;
        if (y > ymax) ymax = y;
        if (x < xmin) xmin = x;
        if (y < ymin) ymin = y;
    }

    PS_Log::ps->set_bounds(xmin,ymin, xmax,ymax);

    LOG(INPUT) << "done reading.  closing file";

    LOG_EXIT();
    file.close();
}

void Collection::write(std::string& file_name) {
    LOG_ENTER(OBJECT,"Collection::write(file)");

    std::ofstream file(file_name.c_str());
    write(file);
    file.close();

    LOG_EXIT();
}

class Item_Writer {
public:
    Item_Writer(std::ostream& os) {
        stream = &os;
        index = 0;
    }

    void action(Item *item) {
        (*os) << "item[" << i++ << "] ID " << item->ID
              << " \"" << item->name << "\""
              << " (x y)=(" << item->x << " " << item->y << ") \""
              << item->label << "\"\n";
    }
private:
    std::ostream *stream;
    int index;
};

class Item_ID_Writer {
public:
    Item_ID_Writer(std::ostream& os) {
        stream = &os;
    }

    void action(Item *item) {
        (*os) << item->ID << " ";
    }
private:
    std::ostream *stream;
};


void Collection::write(std::ostream& os) const {
    LOG_ENTER(OBJECT,"Collection::write()");

    Item_Writer writer(os);
    for_all(begin(), end(), writer);
    os.flush();

    LOG_EXIT();
}

void Collection::write_IDs(std::ostream& os) const {
    LOG_ENTER(OBJECT,"Collection::write()");

    Item_ID_Writer writer(os);
    for_all(begin(), end(), writer);
    os.flush();

    LOG_EXIT();
}

class Item_Distance_Writer {
public:
    Item_Writer(std::ostream& os) {
        stream = &os;
    }

    void action(Item *item, Item *jtem) {
        double dist = item->distance(jtem);
        (*os) << dist << " ";
    }
private:
    std::ostream *stream;
};

void Collection::save_distances(std::string& filename) {
    LOG_ENTER(OBJECT,"Collection::save_distances");

    std::ofstream file(filename.c_str());
    if (!file) {
        std::cerr << "Can't write to " << filename << "\n";;
        exit(EXIT_FAILURE);
    }

    Item_Distances_Writer writer(file);
    for_all_pairs(begin(), end(),
                  begin(), end(),
                  writer);

    LOG_EXIT();
    file.close();
}
